﻿
using EmperialApps.WeatherSpark.Data;
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace EmperialApps.WeatherSpark.Service {

    /// <summary>Renders the graph for the Windows Phone live tile.</summary>
    public partial class TileImage : UserControl {

        /// <summary>Initializes a new instance of the <see cref="TileImage"/> class.</summary>
        public TileImage( ) {
            InitializeComponent( );
        }


        /// <summary>Saves a PNG image of the tile to the specified stream.</summary>
        public static void SaveImage( Stream stream, Forecast forecast, Units units, ForecastDisplayMode mode ) {
            // Create tile and display current values.
            var tile = GetTileImage( );
            tile.DisplayValues( forecast, units, mode );

            // Render control to bitmap.
            int width = (int)tile.Width;
            int height = (int)tile.Height;
            tile.Measure( new Size( width, height ) );
            tile.Arrange( new Rect( 0, 0, width, height ) );
            var bitmap = new RenderTargetBitmap( width, height, 96, 96, PixelFormats.Pbgra32 );
            bitmap.Render( tile );

            // Encode bitmap as PNG image and return bytes.
            var encoder = new PngBitmapEncoder( );
            encoder.Frames.Add( BitmapFrame.Create( bitmap ) );
            encoder.Save( stream );
            stream.Flush( );
        }


        #region Private Members

        private const int HoursPerDay = 24;

        private DataValues _temperature;


        private static TileImage GetTileImage( ) {
            const int RetryCount = 6;

            TileImage tile = null;
            for( int i = 0; tile == null && i < RetryCount; ++i ) {
                try { tile = new TileImage( ); }
                catch( ArgumentException ) { }
            }

            return tile;
        }

        private void DisplayValues( Forecast forecast, Units units, ForecastDisplayMode mode ) {
            // Get time values for current mode.
            DateTimeOffset now = DateTimeOffset.Now.ToOffset( forecast.Start.Offset );
            DateTimeOffset date = now.GetDateOffset( );
            DateTimeOffset begin = mode == ForecastDisplayMode.Day ? date : date.AddHours( now.Hour - HoursPerDay / 3 );
            double hourOffset = 1 + (int)now.HoursFrom( forecast.Start );

            // Get temperature and setup time scale.
            double temperatureHeight = this.GraphRow.Height.Value;
            forecast.GetTemperatureValues( units, ref this._temperature );
            double currentTemperature = this._temperature.GetInterpolatedValue( hourOffset, ConvertValue.Identity );
            var timeScale = new Scale( false, this.Width, new double[] { 0, HoursPerDay } );

            // Update current/high/low temperature labels.
            {
                string temperatureUnit = units.GetTemperatureSymbol( );
                this.CurrentTemperatureLabel.Text = string.Format( "{0:0}\u00b0{1}", currentTemperature, temperatureUnit );

                double low, high;
                var extremes = this._temperature.TryGetExtremeValues( forecast.Start, now, minimumCount: 0 );
                if( extremes.TryGetValues( out low, out high ) ) {
                    this.LowTemperatureLabel.Text = string.Format( "{0:0}\u00b0", low );
                    this.HighTemperatureLabel.Text = string.Format( "{0:0}\u00b0", high );
                }
                else {
                    this.LowTemperatureLabel.Text = "";
                    this.HighTemperatureLabel.Text = "";
                }
            }

            // Draw temperature trace.
            {
                double beginOffset = begin.HoursFrom( forecast.Start );
                int extraSamples = Math.Max( 0, (int)Math.Ceiling( beginOffset / forecast.Interval ) );
                int beginIndex = extraSamples - Math.Sign( extraSamples );
                int beginCount = HoursPerDay / forecast.Interval + 3;
                int beginPadding = beginIndex * forecast.Interval - (int)beginOffset;
                var displayTemperature = this._temperature.Skip( beginIndex ).Take( beginCount );
                var temperatureScale = new Scale( true, temperatureHeight, displayTemperature, 0.5 );

                var interpolation = new AkimaInterpolation( displayTemperature.Select( temperatureScale.ToScreen ), forecast.Interval );
                var points = interpolation.Interpolate( timeScale, beginPadding ).Select( p => new Point( p.Item1, p.Item2 ) );
                this.TemperatureTrace.Points = new PointCollection( points );

                var currentTimeTransform = (TranslateTransform)this.CurrentTimeMarker.RenderTransform;
                currentTimeTransform.X = timeScale.ToScreen( hourOffset - beginOffset );
                currentTimeTransform.Y = temperatureScale.ToScreen( currentTemperature );

                if( mode == ForecastDisplayMode.Hour ) {
                    double midnightHour = date.AddDays( 1 ).HoursFrom( begin ) % HoursPerDay;
                    this.MidnightLine.Y2 = temperatureHeight;
                    this.MidnightLine.X1 = this.MidnightLine.X2 = timeScale.ToScreen( midnightHour );
                }
            }

            // Set nighttime clip region.
            {
                var location = forecast.Location;
                var yesterday = Daylight.Calculate( location, date.AddDays( -1 ) );
                var today = Daylight.Calculate( location, date );
                var tomorrow = Daylight.Calculate( location, date.AddDays( 1 ) );
                double lastNightStart = timeScale.ToScreen( yesterday.Item2.HoursFrom( begin ) );
                double lastNightEnd = timeScale.ToScreen( today.Item1.HoursFrom( begin ) );
                double nightStart = timeScale.ToScreen( today.Item2.HoursFrom( begin ) );
                double nightEnd = timeScale.ToScreen( tomorrow.Item1.HoursFrom( begin ) );

                var clip = (PathGeometry)this.NightBackground.Clip;
                clip.Figures = new PathFigureCollection {
                    new PathFigure(
                        new Point( lastNightStart, 0 ), new[] { new PolyLineSegment( new[] {
                        new Point( lastNightEnd, 0 ),
                        new Point( lastNightEnd, temperatureHeight ),
                        new Point( lastNightStart, temperatureHeight ) }, isStroked: false ) },
                        closed: true ),
                    new PathFigure(
                        new Point( nightStart, 0 ), new[] { new PolyLineSegment( new[] {
                        new Point( nightEnd, 0 ),
                        new Point( nightEnd, temperatureHeight ),
                        new Point( nightStart, temperatureHeight ) }, isStroked: false ) },
                        closed: true )
                };
            }
        }

        #endregion
    }

}
